(module util-functions scheme
;; tells scheme to provide everything in this file to anyone who requires it
(provide (all-defined-out))
;; we need the test data for check-expects here
(require "data.scm")
;; we need world.ss for check-expect, etc.
(require (lib "world.ss" "teachpack" "htdp"))
(require lang/htdp-intermediate-lambda)
;;
;; @utilityfunctions
;; Utility Functions
;;;;;;;;;;;;;;;;;;;;;

;; lookup-territory : Symbol [Listof Territory] -> Territory | #f
;; attempts to find the given territory from the given list of territories.
(define (lookup-territory findme tofind)
  (cond [(empty? tofind) #f]
        [(symbol=? findme (territory-name (car tofind))) (car tofind)]
        [else (lookup-territory findme (cdr tofind))]))

(check-expect (lookup-territory 'russia territory-list-base) ter-russia)
(check-expect (lookup-territory 'canada territory-list-base) ter-canada)
(check-expect (lookup-territory 'venezuela territory-list-base) #f)

;; adjacent? : Territory Territory -> Boolean
;; if the second territory is in the first's adjacency list it returns true
(define (adjacent? terr1 terr2)
  (local [(define name (territory-name terr2))]
   (ormap (lambda (a-territory)
            (symbol=? a-territory
                      name))
          (territory-neighbors terr1))))

(check-expect (adjacent? ter-usa ter-mexico)
              true)
(check-expect (adjacent? ter-usa ter-france)
              false)

;; change-territory-units/territory : Territory Number -> Territory
;; returns a new territory with everything the same except the units is set to
;; new-units
(define (change-territory-units/territory terr new-units)
  (make-territory (territory-name terr)
                  (territory-continent terr)
                  new-units
                  (territory-neighbors terr)
                  (territory-player terr)))

(check-expect (change-territory-units/territory g-ter-canada 1)
              g-ter-canada-post-attack)
(check-expect (change-territory-units/territory g-ter-usa 7)
              g-ter-usa-post-attack)
(check-expect (change-territory-units/territory g-ter-mideast 4)
              g-ter-mideast-post-attack)
(check-expect (change-territory-units/territory g-ter-africa-north 1)
              g-ter-africa-north-post-attack)

;; change-territory-owner/territory : Territory Player -> Territory
;; returns a new territory owned by the new player
(define (change-territory-owner/territory a-territory new-player)
  (make-territory (territory-name a-territory)
                  (territory-continent a-territory)
                  (territory-units a-territory)
                  (territory-neighbors a-territory)
                  new-player))
#|
(check-expect (change-territory-owner/territory ter-france 'red)
              (make-territory 'france
                              'europe
                              0
                              adj-france
                              'red
                              (update-widget-border (territory-widget ter-france) 'red)))
(check-expect (change-territory-owner/territory ter-usa 'blue)
              (make-territory 'usa
                              'america
                              0
                              adj-usa
                              'blue
                              widget-usa))|#

;;@needstests just no time


;; is-territory-name? : Symbol Territory -> Boolean
;; checks if the supplied symbol is equal to the territory's name field
(define (is-territory-name? a-name a-territory)
  (symbol=? (territory-name a-territory) a-name))

(check-expect (is-territory-name? 'france ter-france) true)
(check-expect (is-territory-name? 'usa ter-mexico) false)

;; find/replace-proc : (X -> Boolean) (X -> X) [ListOf X] -> [ListOf X]
;; finds all elements that pass the predicate and executes modifier on it
(define (find/replace-proc predicate modifier lox)
  (map (lambda (x) (cond [(predicate x) (modifier x)]
                         [else x]))
       lox))

(check-expect (find/replace-proc even? add1 (build-list 5 add1))
              (list 1 3 3 5 5))
(check-expect (find/replace-proc (lambda (x) (symbol=? x 'red))
                                 (lambda (x) 'blue)
                                 '(red red green red))
              '(blue blue green blue))
  
;; sub2 : (Number -> Number)
;; subtracts 2 from a value.
(define sub2 (lambda (x) (- x 2)))

(check-expect (sub2 2) 0)
(check-expect (sub2 17) 15)

;; pred1 : Number -> Number
;; A predicate used in phase 2.
(define (pred1 n) (if (>= n 3) 3 2))
 
(check-expect (pred1 1) 2)
(check-expect (pred1 5) 3)
  
;; [Listof Number] [Listof Number] -> Boolean
;; predicates below : used in calculating the losses in armies for phase 2 functions.
(define (pred-t1-sub2 a-dice d-dice) (and (>= (first d-dice) (first a-dice)) (>= (second d-dice) (second a-dice))))
(define (pred-t2-sub2 a-dice d-dice) (and (> (first a-dice) (first d-dice)) (> (second a-dice) (second d-dice))))
(define (pred-1-and-1 a-dice d-dice) (or (and (> (first a-dice) (first d-dice)) (>= (second d-dice) (second a-dice)))
                                         (and (>= (first d-dice) (second a-dice)) (> (second a-dice) (second d-dice)))))


;; replace-territory/board : Board Territory -> Board | false
;; replaces the new-territory with the equivalent territory in the board
;; @needstests The second test?
(define (replace-territory/board brd t)
	(local [(define terrlist (replace-territory (board-territories brd) t))]
	(cond [(list? terrlist)
                (make-board
                  (replace-territory (board-territories brd) t)
                  (board-continents brd)
                  (board-players brd)
                  (board-state brd))]
               [else #f])))

(check-expect (replace-territory/board board-blank (make-territory 'canada 'america 0 adj-france
                                                                   'unowned))
              (make-board (list (make-territory 'canada 'america 0 adj-france 'unowned)
                                ter-usa ter-mexico ter-sa ter-france ter-germany ter-mideast
                                ter-africa-north ter-africa-south ter-russia ter-china ter-australia)
                          continent-list
                          g-players
                          g-state-p1))
#;(check-expect (replace-territory/board (make-board 1 '() '() '())
                                         (make-territory 'canada 'america 0 adj-france 'unowned
                                                         ))
              #f)


;; replace-territory : [listof Territory] Territory → [listof Territory] | false
;; replaces the territory in the list with the same name as the given territory with
;; the given territory
(define (replace-territory lot t)
  (cond [(empty? lot) lot]
        [(symbol=? (territory-name (first lot)) (territory-name t))
         (cons t (rest lot))]
        [else (cons (first lot) (replace-territory (rest lot) t))]))

(check-expect (replace-territory test-ter-list fake-canada) (list fake-canada ter-usa))
(check-expect (replace-territory test-ter-list ter-germany) test-ter-list)


;; change-territory-owner/board : Symbol Player Board -> Board
;; updates the board's territory list to reflect the new owner of the named
;; territory
;; ASSUME: territory-name-to-find is a name of a territory on the board
(define (change-territory-owner/board territory-name-to-find new-player board)
  (make-board (find/replace-proc (lambda (a-territory)
                                   (symbol=? (territory-name a-territory)
                                             territory-name-to-find))
                                 (lambda (a-territory)
                                  (change-territory-owner/territory a-territory
                                                                   new-player))
                                 (board-territories board))
              (board-continents board)
              (board-players board)
              (board-state board)))

(check-expect (change-territory-owner/board 'france 'blue board-blank)
              (make-board (list ter-canada ter-usa ter-mexico ter-sa
                                (make-territory 'france 'europe 0 adj-france
                                                'blue)
                                ter-germany ter-mideast ter-africa-north
                                ter-africa-south ter-russia ter-china
                                ter-australia)
                          continent-list
                          g-players
                          g-state-p1))

(check-expect (change-territory-owner/board 'usa 'blue board-blank)
              (make-board (list ter-canada
                                (make-territory 'usa 'america 0 adj-usa
                                                'blue )
                                ter-mexico ter-sa ter-france
                                ter-germany ter-mideast ter-africa-north
                                ter-africa-south ter-russia ter-china
                                ter-australia)
                          continent-list
                          g-players
                          g-state-p1))

;; disperse : UI-Element Board -> [Listof Territory]
;; Changes a clicked territory's unit number to 1.
;; @needstests
(define (disperse ui b)
  (replace-territory (board-territories b)
                     (change-territory-units/territory (lookup-territory (territory-name ui)
                                                                         (board-territories b)) 1)))

;; get-ui-element : Posn Board → ui-element OR #f
;; to return the UI Element containing the given posn

#|(check-expect (get-ui-element (make-posn 50 50) g-board-p1) g-ter-canada)
(check-expect (get-ui-element (make-posn 70 140) g-board-p1) g-ter-usa)|#

;; contains? : posn ui-element → boolean
;; determines if the given posn is within the given ui element
;; @performance **We repeat four lines here.  Perhaps we should abstract them
;;								into a posn-within-rectangle class
;; @wtf **Can we seriously change the name of this method?  I see it everytime
;;        and think that its dealing with lists

#|
(check-expect (contains? (make-posn 100 100) ter-canada) true)
(check-expect (contains? (make-posn 500 500) ter-canada) false)
(check-expect (contains? (make-posn 600 400) ter-australia) true)
(check-expect (contains? (make-posn 300 200) ter-china) false)
(check-expect (contains? (make-posn 250 400) ter-africa-south) true)|#

;; get-nw-corner : Territory -> Posn
;; returns the north west corner of a given territory
#;(define (get-nw-corner ter)
  (make-posn (- (widget-x (territory-widget ter))
                (/ (image-width (first (widget-image (territory-widget ter))))
                   2))
             (- (widget-y (territory-widget ter))
                (/ (image-height (first (widget-image (territory-widget ter))))
                   2))))

#;(check-expect (get-nw-corner ter-canada)
              (make-posn 20 20))
#;(check-expect (get-nw-corner ter-russia)
              (make-posn 345 120))

;; goto-state : Board Number -> Board
;; a short hand wrapper for change-board-state
(define (goto-state board state)
  (cond [(equal? state 1) (change-board-state board
                                               g-state-p1)]
        [(equal? state 2) (change-board-state board g-state-p2-o)]
        [(equal? state 3) (change-board-state board g-state-p3-o)]
        [else (error 'goto-state
                     "Attempted to change to a state that does not exist")]))

(check-expect (goto-state board-blank 2)
              board-blank-state2)
(check-expect (goto-state board-blank 1)
              board-blank)
(check-expect (goto-state board-blank-state2 1)
              board-blank)
(check-expect (goto-state g-board-p1 3)
              running-game-p3-o)

;; change-board-state : Board State -> Board
;; creates a new board with all the same fields except the state is changed
(define (change-board-state brd state)
  (make-board (board-territories brd)
              (board-continents brd)
              (board-players brd)
              state))

(check-expect (change-board-state board-blank g-state-p2-o)
              (make-board territory-list-base
                                continent-list
                                g-players
                                g-state-p2-o))
(check-expect (change-board-state board-blank g-state-p1)
              (make-board territory-list-base
                                continent-list
                                g-players
                                g-state-p1))

;; state-name : Board -> Symbol
;; returns the state's name ('p1, 'p2-origin ...)
(define (board-state-name board)
  (first (board-state board)))

(check-expect (board-state-name board-blank)
              'p1)
(check-expect (board-state-name board-blank-state2)
              'p2-origin)

;; state-ds1 : Board -> ??
;; Returns whatever happens to be inside the first data slot in the state of
;; the given board
(define (sds1 board)
  (second (board-state board)))

(check-expect (sds1 board-blank)
              10)
(check-expect (sds1 running-game-p2-victim)
              g-ter-germany)

;; sds2 : Board -> ??
;; returns whatever happens to be inside the second data slot in the state of
;; the given board
(define (sds2 board)
  (third (board-state board)))

(check-expect (sds2 board-blank)
              empty)
(check-expect (sds2 running-game-p2-victim)
              empty)
(check-expect (sds2 running-game-p3-d-0)
              0)
  
;; sds3 : Board -> ??
;; Returns whatever happens to be inside the third data slot in the state of
;; the given board
(define (sds3 board)
  (fourth (board-state board)))
;@needstests

;; board-state-button-list : Board -> [ListOf Button]
;; returns the board state's list of buttons
(define (board-state-button-list board)
  (sds3 board))

#|(check-expect (board-state-button-list board-blank)
              empty)
(check-expect (board-state-button-list running-game-p2-victim)
              phase2-victim-buttons)
(check-expect (board-state-button-list running-game-p3-o)
              phase3-origin-buttons)|#

;; get-last : [Listof X] -> X
;; gets the last element in a list.
(define (get-last l)
  (cond [(empty? l) l]
        [(empty? (rest l)) (first l)]
        [else (get-last (cdr l))]))

(check-expect (get-last '(1 2 3 4 5)) 5)
(check-expect (get-last '()) '())


;; remove-last : [Listof X] -> [Listof X]
;; removes the last item from the list.
(define (remove-last l)
  (cond [(empty? l) l]
        [(empty? (rest l)) empty]
        [else (cons (car l) (remove-last (cdr l)))]))

(check-expect (remove-last '(1 2 3 4)) '(1 2 3))
(check-expect (remove-last '()) '())

;; current-player : Board -> Player
;; returns the current player
(define (current-player board)
  (first (board-players board)))

(check-expect (current-player board-blank) 'red)
(check-expect (current-player board-blank-player-blue) 'blue)

;; map-terr-list : [Listof Territory] Board -> Board
;; Maps replace-territory over a list of territories.
  (define (map-terr-list tl board)
    (cond [(empty? tl) board]
          [else (map-terr-list (rest tl) (replace-territory/board board (first tl)))]))
  
  (check-expect (map-terr-list '() board-blank) board-blank)
  (check-expect (map-terr-list (list (make-territory 'canada 'america 0 adj-canada
                                                     'unowned)
                                     (make-territory 'france 'europe 0 adj-france
                                                     'unowned))
                               board-blank)
                (make-board
                 (list (make-territory 'canada 'america 0 adj-canada 'unowned)
                       ter-usa ter-mexico ter-sa
                       (make-territory 'france 'europe 0 adj-france 'unowned)
                       ter-germany ter-mideast
                       ter-africa-north ter-africa-south ter-russia ter-china ter-australia)
                 continent-list
                 g-players
                 g-state-p1))
  
  ;; territories-owned : Player [listof Territory] number → number
;; determines how many territories are owned by the given player
;; now complete with accumulator and corresponding wrapper function!
(define (territories-owned-acc player lot acc)
  (cond [(empty? lot) acc]
        [(territory-owned? player (car lot))
         (territories-owned-acc player (cdr lot) (add1 acc))]
        [else (territories-owned-acc player (cdr lot) acc)]))

(define (territories-owned player lot)
  (territories-owned-acc player lot 0))

(check-expect (territories-owned 'red g-territory-list-current) 6)
(check-expect (territories-owned 'blue g-territory-list-current) 6)
(check-expect (territories-owned 'gray g-territory-list-current) 0)

;; territory-owned? : Player Territory → boolean
;; determines if the the given player owns the given territory
(define (territory-owned? player t)
  (symbol=? (territory-player t) player))

(check-expect (territory-owned? 'red g-ter-canada) false)
(check-expect (territory-owned? 'blue g-ter-africa-south) true)
(check-expect (territory-owned? 'what g-ter-russia) false)

;; is-neighbor? : [Listof Symbol] Territory -> Boolean
;; Given a list of Ts (neighbors from another T) and a separate T, are they neighbors?
(define (is-neighbor? lot t2)
  (cond [(empty? lot) false]
        [(symbol=? (first lot) (territory-name t2)) true]
        [else (is-neighbor? (rest lot) t2)]))

(check-expect (is-neighbor? (territory-neighbors ter-china) ter-germany) false)
(check-expect (is-neighbor? (territory-neighbors ter-usa) ter-canada) true)

;; set-player : Territory Player -> Territory
;; sets the player of of a territory
(define (set-player ter plyr)
  (make-territory (territory-name ter)
                  (territory-continent ter)
                  (territory-units ter)
                  (territory-neighbors ter)
                  plyr))

(check-expect (set-player ter-canada 'p1) (make-territory 'canada 'america 0 adj-canada 'p1 ))
(check-expect (set-player ter-france 'p3) (make-territory 'france 'europe 0 adj-france 'p3 ))

;; continents-owned : Player [listof Territory] [listof Continent] → [listof Continent]
;; returns all continents completely owned by a player
(define (continents-owned player lot loc)
    (cond [(empty? loc) empty]
                  [(continent-owned? player lot (continent-name (car loc)))
                            (cons (car loc) (continents-owned player lot (cdr loc)))]
                          [else (continents-owned player lot (cdr loc))]))

(check-expect (continents-owned 'red g-territory-list-current continent-list) (list mini-asia))
(check-expect (continents-owned 'blue g-territory-list-current continent-list) (list))
(check-expect (continents-owned 'unowned territory-list-base continent-list)
                            (list mini-asia mini-europe mini-america))

;; continent-owned? : Player [listof Territory] Continent → boolean
;; determines if the given player controls all territories that are part of the given continent
(define (continent-owned? player lot cont)
    (cond [(empty? lot) true]
                  [(and (symbol=? player (territory-player (car lot)))
                                      (symbol=? cont (territory-continent (car lot))))
                            (continent-owned? player (cdr lot) cont)]
                          [(symbol=? cont (territory-continent (car lot))) false]
                                  [else (continent-owned? player (cdr lot) cont)]))

(check-expect (continent-owned? 'blue g-territory-list-current 'europe) false)
(check-expect (continent-owned? 'red g-territory-list-current 'asia) true)
(check-expect (continent-owned? 'green g-territory-list-current 'nowhere) true)
(check-expect (continent-owned? 'blue empty 'nowhere) true)



;; end-game? : [Listof Territories] -> Symbol | false
;; returns the winning player if the board 
;; has been conquered, false otherwise.
;; It is assumed that the list of territories is NOT EMPTY.
;; Since this function is called in a later phase, it is safe.
(define (end-game? terrs)
  (local [(define winner (territory-player (car terrs)))]
    (end-game-help terrs winner)))
  
;; end-game-help : {Listof Territories] Symbol -> Symbol | false
;; Helps compute endgame.
(define (end-game-help terrs winner)
  (cond [(empty? terrs) winner]
          [(not (symbol=? winner (territory-player (car terrs)))) false]
          [else (end-game-help (cdr terrs) winner)]))
  
(check-expect (end-game? (list ter-france)) 'unowned)
(check-expect (end-game? (list ter-canada ter-usa)) 'unowned)
  

;; zi? : X -> Boolean
;; Is this thing a zipped up image, just waiting to be unleashed upon the world? (/universe?)
(define (zi? x) (cond [(not (cons? x)) false]
                      [(and (symbol? (first x))
                            (symbol=? (first x) 'zi)) true]
                      [else false]))
(check-expect (zi? (list 'zi 'blah 'blah)) true)
(check-expect (zi? 5) false)
(check-expect (zi? (list 5 4)) false)
  
;; zc? X -> Boolean
;; Is this thing a zipped up color, just waiting to brighten the scene?
(define (zc? x) (cond [(not (cons? x)) false]
                      [(and (symbol? (first x))
                            (symbol=? (first x) 'zc)) true]
                      [else false]))
(check-expect (zc? (list 'zc 45 56 60)) true)
(check-expect (zc? 4) false)
(check-expect (zc? (list 5 6)) false)
  
;; unzip-zi : ZI -> Image | #f
;; unzip me, baby! (images only)
(define (unzip-zi z) (cond [(not (zi? z)) false]
                           [(and (zi? z) (symbol=? 'rectangle (second z)))
                            (apply rectangle (cddr z))]
                           [else false]))
(check-expect (unzip-zi (list 'zi 'rectangle 100 80 'solid 'green)) (rectangle 100 80 'solid 'green))
  
;; unzip-zc : ZC -> Color | #f
;; unzip a color.
(define (unzip-zc z) (cond [(not (zc? z)) false]
                           [(and (zc? z) (symbol=? 'make-color (second z)))
                            (apply make-color (cddr z))]
                          [else false]))
  
(check-expect (unzip-zc (list 'zc 'make-color 30 40 50)) (make-color 30 40 50))

;; mega-unzip : [Listof Anythings] -> [Listof Anythings]
;; Parses through a giant list, looks for zipped up things and unzips away.
(define (mega-unzip l) (big-unzip-image (big-unzip-color l)))
  
;; big-unzip-color : [Listof Anythings] -> [Listof Anythings]
;; This function goes through and unzips the colors only (since you can't apply
;; a zipped color to a shape)
(define (big-unzip-color l)
  (cond [(not (cons? l)) l] 
        [(zc? (first l)) (cons (unzip-zc (first l)) (big-unzip-color (rest l)))]
        [else (cons (big-unzip-color (first l)) (big-unzip-color (rest l)))]))
  
;; big-unzip-image : [Listof Anythings] -> [Listof Anythings]
;; This function, after the colors have been unzipped,
;; applies the image function calls.
(define (big-unzip-image l)
  (cond [(not (cons? l)) l] 
        [(zi? (first l)) (cons (unzip-zi (first l)) (big-unzip-image (rest l)))]
        [else (cons (big-unzip-image (first l)) (big-unzip-image (rest l)))]))

  
(check-expect (mega-unzip (list (list (list 4 5 (list 'zi 'rectangle 40 50 'solid 'red)) (list 'zi 'rectangle 50 60 'solid 'blue)) 5 6 7))
              (list (list (list 4 5 (rectangle 40 50 'solid 'red)) (rectangle 50 60 'solid 'blue)) 5 6 7))
                          
  


;;
;; END OF
;; Utility Functions
;;;;;;;;;;;;;;;;;;;;;

) ;; end of module
